/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.logging.rebind;

import java.util.List;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.SelectionProperty;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * Write the necessary code in the generated logging oracle implementation.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 07 April 2011
 */
public class OPLoggingOracleWriter {

    /**
     * Name of the property flag enabling or disabling all logging.
     */
    protected static final String PROPERTY_LOGGING = "OPLogging.logging";

    /**
     * Value of the property PROPERTY_LOGGING when logging is enabled
     */
    protected static final String PROPERTY_LOGGING_ENABLED = "enabled";

    /**
     * Value of the property PROPERTY_LOGGING when logging is disabled
     */
    protected static final String PROPERTY_LOGGING_DISABLED = "disabled";

    /**
     * Name of the configuration property containing all domains enabled.
     */
    protected static final String PROPERTY_DOMAINS = "OPLogging.domains";

    /**
     * Name of the generated var containing all loggers.
     */
    protected static final String VAR_LOGGERS = "loggers";
    
    /**
     * Name of the generated method filling up the loggers.
     */
    protected static final String METHOD_FILLING_LOGGERS = "fillUpAllLoggers";
    
    /**
     * The source writer to the generated binder.
     */
    protected final SourceWriter sourceWriter;

    /**
     * The logger, passed by the GWT framework.
     */
    protected final TreeLogger logger;

    /**
     * Is logging enabled ?
     */
    protected final boolean loggingEnabled;

    /**
     * All domains that are enabled.
     */
    protected final List<String> domains;
    
    /**
     * Constructor. Get all the OPLogging.* properties and create the corresponding loggers.
     * 
     * @param propertyOracle
     * @param sourceWriter
     * @param logger
     * @throws UnableToCompleteException
     */
    public OPLoggingOracleWriter(final PropertyOracle propertyOracle, final SourceWriter sourceWriter, final TreeLogger logger) throws UnableToCompleteException {
        assert propertyOracle != null : "parameter propertyOracle shouldn't be null";
        assert sourceWriter != null : "parameter sourceWriter shouldn't be null";
        assert logger != null : "parameter logger shouldn't be null";

        this.sourceWriter = sourceWriter;
        this.logger = logger;

        // configure the generator from the OPLogging.* properties
        loggingEnabled = isLoggingEnabledInProperties(propertyOracle);
        domains = getLoggingDomainsFromProperties(propertyOracle);
        assert domains != null : "domains shoudn't be null. It can be empty, but not null";
    }

    /**
     * Read the properties and find if the logging has been enabled or not, through the OPLogging.logging property, in a .gwt.xml file. By default, logging is disabled.
     * 
     * @param propertyOracle
     *            the PropertyOracle, given by the GWT Framework.
     * @return true if the OPLogging.logging property has the enabled value, false otherwise.
     */
    protected boolean isLoggingEnabledInProperties(final PropertyOracle propertyOracle) {
        try {
            final SelectionProperty enabledProperty = propertyOracle.getSelectionProperty(logger, PROPERTY_LOGGING);
            assert enabledProperty != null : "GWT returned a null property for " + PROPERTY_LOGGING;
            final String enabledValue = enabledProperty.getCurrentValue();
            assert enabledValue != null : "GWT returned a null value for property " + PROPERTY_LOGGING;
            if (PROPERTY_LOGGING_ENABLED.equals(enabledValue)) {
                return true;
            } else if (PROPERTY_LOGGING_DISABLED.equals(enabledValue)) {
                return false;
            } else {
                // shouldn't be possible, if the value is wrong then there should be an exception. Well, maybe I messed up the property values?
                assert false : "GWT returned a value for property " + PROPERTY_LOGGING + " that is neither " + PROPERTY_LOGGING_ENABLED + " nor " + PROPERTY_LOGGING_DISABLED;
            }
        } catch (BadPropertyValueException e) {
            // user put a bad value here, as there is a default value in OPLogging.gwt.xml. Tell him, so that it can correct its error.
            logger.log(TreeLogger.ERROR, "The property " + PROPERTY_LOGGING + " can only have two values: " + PROPERTY_LOGGING_ENABLED + " and " + PROPERTY_LOGGING_DISABLED + ". " + e.getMessage());
        }
        // if something went wrong, by default disable logging.
        return false;
    }

    /**
     * Read the properties and find all domains defined through the OPLogging.domains configuration property, in a .gwt.xml file. By default, no domains are defined.
     * 
     * @param propertyOracle
     *            the PropertyOracle, given by the GWT Framework.
     * @return a list of all domains
     */
    protected List<String> getLoggingDomainsFromProperties(final PropertyOracle propertyOracle) {
        try {
            final ConfigurationProperty domainsProperty = propertyOracle.getConfigurationProperty(PROPERTY_DOMAINS);
            assert domainsProperty != null : "GWT returned a null property for " + PROPERTY_DOMAINS;
            return domainsProperty.getValues();
        } catch (BadPropertyValueException e) {
            // shouldn't be possible, the property is defined in OPLogging.gwt.xml. Well, maybe I messed up the property name?
            logger.log(TreeLogger.ERROR, "The property " + PROPERTY_DOMAINS + " is not defined, which is strange as it should be in OPLogging.gwt.xml. " + e.getMessage());
        }
        // something went wrong
        return null;
    }

    /**
     * Write the necessary code in the generated oracle. Several steps are necessary:
     * <ul>
     * <li>generate the hastable containing all loggers</li>
     * <li>generate the constructor, that will call the method filling up the hashtable</li>
     * <li>generate the method filling up the hashtable</li>
     * <li>generate the getLoggerForDomain method</li>
     * </ul>
     */
    public void writeOracle() {
        sourceWriter.println();
        writeLoggers();
        sourceWriter.println();
        writeConstructor();
        sourceWriter.println();
        writeFillingLoggers();
        sourceWriter.println();
        writeGetLoggerForDomain();
        sourceWriter.println();
        
        sourceWriter.commit(logger);
    }

    /**
     * Write and fill up the hashtable containing all loggers.
     */
    protected void writeLoggers() {
        if (!loggingEnabled) { // put something only if logging is enabled
            return;
        }
        sourceWriter.indent();
        sourceWriter.println("protected Map<String, OPLogger> loggers = new HashMap<String, OPLogger>();");
        sourceWriter.outdent();
    }
    
    /**
     * Write the constructor that will call the method to fill up the hashtable containing all loggers.
     */
    protected void writeConstructor() {
        if (!loggingEnabled) { // put something only if logging is enabled
            return;
        }
        sourceWriter.indent();
        sourceWriter.println("public " + OPLoggingOracleGenerator.ORACLE_IMPL_NAME + "() {");
        sourceWriter.indent();
        sourceWriter.println(METHOD_FILLING_LOGGERS + "();");
        sourceWriter.outdent();
        sourceWriter.println("}");
        sourceWriter.outdent();
    }
    
    /**
     * Write the method that will fill up the hashtable containing all loggers.
     */
    protected void writeFillingLoggers() {
        if (!loggingEnabled) { // put something only if logging is enabled
            return;
        }
        sourceWriter.indent();
        sourceWriter.println("protected void " + METHOD_FILLING_LOGGERS + "() {");
        sourceWriter.indent();
        for (String domain : domains) {
            sourceWriter.println("loggers.put(\"" + domain + "\", new OPLogging.OPLoggerImpl(\"" + domain + "\"));");
        }
        sourceWriter.outdent();
        sourceWriter.println("}");
        sourceWriter.outdent();        
    }
    
    /**
     * Write the getLoggerForDomain() method.
     */
    protected void writeGetLoggerForDomain() {
        sourceWriter.indent();
        sourceWriter.println("public OPLogger getLoggerForDomain(final String domain) {");
        sourceWriter.indent();
        if (!loggingEnabled) {
            sourceWriter.println("return OPLogging.nullLogger;");
        } else {
            sourceWriter.println("final OPLogger logger = " + VAR_LOGGERS + ".get(domain);");
            sourceWriter.println();
            sourceWriter.println("if (logger != null) {");
            sourceWriter.indent();
            sourceWriter.println("return logger;");
            sourceWriter.outdent();
            sourceWriter.println("} else {");
            sourceWriter.indent();
            sourceWriter.println("return OPLogging.nullLogger;");
            sourceWriter.outdent();
            sourceWriter.println("}");
        }
        sourceWriter.outdent();
        sourceWriter.println("}");
        sourceWriter.outdent();
    }

}
